unit CPGDIPlusPlotCanvas;

interface

uses Types, Classes, Controls, Graphics,
  CPPlotCanvas, CPBase, GDIPAPI, GDIPOBJ;

type
  { This class implements GDI painting methods defined by TCPPlotCanvas.
    Canvas units are interpreted as pixels. }
  TCPGDIPlusPlotCanvas = class(TCPPlotCanvas)
  private
    FGraphics: TGPGraphics;
    FCanvasGraphics: TGPGraphics;
    FPen: TGPPen;
    FBrush: TGPBrush;
    FFontBrush: TGPBrush;
    FGPFont: TGPFont;
    FFontFamily: TGPFontFamily;
    FPoints: array of TGPPointF;
    { Rectangle used in HorizLine() and VertLine() for optimization purposes }
    FLineRect: TCPRect;
    FBitmap: TGPBitmap;
    { Text placement in GDI+ differs from GDI }
    FTextPadding: Single;
  protected
    procedure Resize; override;
    procedure SetBrushColor(const Value: TColor); override;
    procedure FontChanged; override;
    procedure SetPenColor(const Value: TColor); override;
    procedure SetPenWidth(const Value: TCPFloat); override;
  public
    { Create the canvas on the referenced parent control.
      @param Control Plotter control) }
    constructor Create(Control: TCustomControl;
      UnitsPerInch: TCPFloat); override;
    destructor Destroy; override;
    procedure DrawLineSegments(Points: TCPFloatArray; Left,
      Right: TCPFloat; StepSize: Double); override;
    procedure MoveTo(X, Y: TCPFloat); override;
    procedure LineTo(X, Y: TCPFloat); override;
    procedure HorizLine(Y: TCPFloat); override;
    procedure VertLine(X: TCPFloat); override;
    procedure Ellipse(const Bounds: TCPBounds); override;
    procedure Rectangle(const Bounds: TCPBounds); override;
    procedure FrameRect(const Bounds: TCPBounds); override;
    procedure FillRect(const Bounds: TCPBounds); override;
    procedure TextOut(X, Y: TCPFloat; const Text: string); override;
    procedure BeginDraw; override;
    procedure EndDraw; override;
end;

implementation

uses Math, Windows, CPPlotter, SysUtils;


{ TCPGDIPlusPlotCanvas }

constructor TCPGDIPlusPlotCanvas.Create(Control: TCustomControl;
  UnitsPerInch: TCPFloat);
begin
  inherited;
  FControl.ControlStyle := FControl.ControlStyle + [csOpaque];
  FControl.DoubleBuffered := false;
  FPoints := nil;
  FCanvasGraphics := nil;
  FPen := nil;
  FBrush := nil;
  FFontBrush := nil;
  FGPFont := nil;
  FFontFamily := nil;
  FontChanged;
end;

destructor TCPGDIPlusPlotCanvas.Destroy;
begin
  FBitmap.Free;
  FPen.Free;
  FBrush.Free;
  FFontBrush.Free;
  FGPFont.Free;
  FFontFamily.Free;
  Finalize(FPoints);
  inherited;
end;

procedure TCPGDIPlusPlotCanvas.Ellipse(const Bounds: TCPBounds);
begin
  FGraphics.FillEllipse(FBrush, Bounds.Left, Bounds.Top,
    Bounds.Width, Bounds.Height);
  FGraphics.DrawEllipse(FPen, Bounds.Left, Bounds.Top,
    Bounds.Width, Bounds.Height);
end;

procedure TCPGDIPlusPlotCanvas.FillRect(const Bounds: TCPBounds);
begin
  FGraphics.FillRectangle(FBrush, Bounds.Left, Bounds.Top,
    Bounds.Width, Bounds.Height);
end;

procedure TCPGDIPlusPlotCanvas.FrameRect(const Bounds: TCPBounds);
begin
  FGraphics.DrawRectangle(FPen, Bounds.Left, Bounds.Top,
    Bounds.Width, Bounds.Height);
end;

procedure TCPGDIPlusPlotCanvas.HorizLine(Y: TCPFloat);
begin
  FGraphics.DrawLine(FPen, FLineRect.Left, Y, FLineRect.Right, Y);
end;

procedure TCPGDIPlusPlotCanvas.LineTo(X, Y: TCPFloat);
begin
  FGraphics.DrawLine(FPen, FDrawPos.X, FDrawPos.Y, X, Y);
  //FCanvas.LineTo(CPClipRound(X), CPClipRound(Y));
  inherited;
end;

procedure TCPGDIPlusPlotCanvas.MoveTo(X, Y: TCPFloat);
begin
  inherited;
  FCanvas.MoveTo(CPClipRound(X), CPClipRound(Y));
end;

procedure TCPGDIPlusPlotCanvas.Rectangle(const Bounds: TCPBounds);
begin
  FGraphics.FillRectangle(FBrush, Bounds.Left, Bounds.Top,
    Bounds.Width, Bounds.Height);
  FGraphics.DrawRectangle(FPen, Bounds.Left, Bounds.Top,
    Bounds.Width, Bounds.Height);
end;

procedure TCPGDIPlusPlotCanvas.SetBrushColor(const Value: TColor);
begin
  inherited;
  FBrush := TGPSolidBrush.Create(ColorRefToARGB(FBrushColor));
end;

procedure TCPGDIPlusPlotCanvas.FontChanged;
var
  FontStyle: Integer;
begin
  inherited;
  FGPFont.Free;
  FFontFamily.Free;
  FFontFamily := TGPFontFamily.Create(FFont.Name);
  FontStyle := FontStyleRegular;
  if (fsBold in FFont.Style) then
    FontStyle := FontStyle or FontStyleBold;
  if (fsItalic in FFont.Style) then
    FontStyle := FontStyle or FontStyleItalic;
  if (fsUnderline in FFont.Style) then
    FontStyle := FontStyle or FontStyleUnderline;
  if (fsStrikeOut in FFont.Style) then
    FontStyle := FontStyle or FontStyleStrikeOut;
  FGPFont := TGPFont.Create(FFontFamily, FFontHeight, FontStyle,
    UnitPixel);
end;

procedure TCPGDIPlusPlotCanvas.SetPenColor(const Value: TColor);
begin
  inherited;
  FPen.Free;
  FFontBrush.Free;
  FPen := TGPPen.Create(ColorRefToARGB(FPenColor), FPenDiscreteUnitWidth);
  FFontBrush := TGPSolidBrush.Create(ColorRefToARGB(FPenColor));
end;

procedure TCPGDIPlusPlotCanvas.TextOut(X, Y: TCPFloat; const Text: string);
var
  Origin: TGPPointF;
begin
  FTextPadding := FGPFont.GetHeight(FGraphics) * 0.15;
  Origin.X := X - FTextPadding;
  Origin.Y := Y - 1;
  FGraphics.DrawString(Text, Length(Text), FGPFont, Origin, FFontBrush);
end;

procedure TCPGDIPlusPlotCanvas.VertLine(X: TCPFloat);
begin
  FGraphics.DrawLine(FPen, X, FLineRect.Top, X, FLineRect.Bottom);
end;

procedure TCPGDIPlusPlotCanvas.SetPenWidth(const Value: TCPFloat);
begin
  inherited;
  FPen := TGPPen.Create(FPenColor, FPenDiscreteUnitWidth);
end;

procedure TCPGDIPlusPlotCanvas.Resize;
begin
  FLineRect.Top := FBounds.Top + FBounds.Height;
  FLineRect.Bottom := FBounds.Top;
  FLineRect.Left := FBounds.Left;
  FLineRect.Right := FBounds.Left + FBounds.Width;
end;

procedure TCPGDIPlusPlotCanvas.BeginDraw;
begin
  inherited;
  FCanvasGraphics := TGPGraphics.Create(FCanvas.Handle);
  FBitmap := TGPBitmap.Create(FControl.Width, FControl.Height,
    FCanvasGraphics);
  FGraphics := FGraphics.FromImage(FBitmap);
  FGraphics.Clear(ColorRefToARGB(FColor));
  FGraphics.SetTextRenderingHint(TextRenderingHintAntiAlias);
  FGraphics.SetSmoothingMode(SmoothingModeHighQuality);
end;

procedure TCPGDIPlusPlotCanvas.EndDraw;
begin
  FCanvasGraphics.DrawImage(FBitmap, 0, 0);
  FreeAndNil(FGraphics);
  FreeAndNil(FCanvasGraphics);
  FreeAndNil(FBitmap);
  inherited;
end;

procedure TCPGDIPlusPlotCanvas.DrawLineSegments(Points: TCPFloatArray;
  Left, Right: TCPFloat; StepSize: Double);
var
  Pt: TCPPoint;
  Index, PointCount, Count: Integer;
  DrawingLine: Boolean;
  YMin, YMax: TCPFloat;
begin
  if (Length(Points) > Length(FPoints)) then
    SetLength(FPoints, Length(Points));
  PointCount := 0;
  Index := 0;
  DrawingLine := true;
  Pt.X := Left;
  YMin := FBounds.Top - FBounds.Height;
  YMax := FBounds.Top + FBounds.Height * 2;
  Count := Length(Points);
  while (Index < Count) do
  begin
    Pt.Y := Points[Index];
    if (IsNan(Pt.Y) = false) and
       (IsInfinite(Pt.Y) = false) then
    begin
      FPoints[PointCount].X := Pt.X;
      FPoints[PointCount].Y := EnsureRange(Pt.Y, YMin, YMax);
      Inc(PointCount);
      if not DrawingLine then
      begin
        if (PointCount > 1) then
          FGraphics.DrawLines(FPen, PGPPointF(@FPoints[0]), PointCount);
        PointCount := 0;
        DrawingLine := true;
      end;
    end
    else
      // Interrupt line, due to undefined value in definition range:
      DrawingLine := false;
    // Move to next graph point:
    Pt.X := Pt.X + StepSize;
    Inc(Index);
  end;
  if (PointCount > 0) then
    FGraphics.DrawLines(FPen, PGPPointF(@FPoints[0]), PointCount);
end;

end.

